## @package karboom.classes
# Base classes
#
# A bunch of base classes that are extended in other files.
#

from karboom import errors

import cairo

## Base class
#
# All classes inherit from this.
#
class Base:
    
    ## Basic constructor
    #
    # This constructor sets object attributes according to kwargs.
    # @param kwargs: Dictionary of attributes to set, in format attribute=value 
    #  
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
    
    ## Basic representation return
    #
    # This function returns object data representations.
    #    
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, ', '.join(['%s=%s' % (k, repr(v)) for k, v in self.__dict__.items()]))

## Renderable
#
# All classes that have an __render__ inherit from this.
#
class Renderable(Base):
    
    ## Current frame
    frame = 0
    
    ## Frames per second
    fps = 72
    
    ## Size, in a 2-tuple formatted (width, height)
    size = (640, 480)
    
    ## Cairo context - this will be set on-the-fly at render time
    context = None
    
    ## Position vector - this will be set on-the-fly at render time
    position = (0, 0) 
    
    ## Layer, this will only affect classes that can inherit top layers
    layer = None
    
    ## Render
    #
    # Automatically invoked function when aggregate render functions are called.
    #
    def __render__(self): pass

## Basic layer
#
# This class is used to draw text, vectors or rasters but should not be used directly. States are attached to this class and rendered.
# 
# layerObject = Layer(states=[stateObject])
#
class Layer(Renderable):
    
    ## Layer position
    position = (0.0, 0.0)
    
    ## Level of layer
    level = 0.0
        
    ## List of states attached to the layer
    states = []
    
    ## List of types in the timing file to apply the layer to
    types = []
    
    ## Start time of layer
    starttime = 0.0
    
    ## End time of layer
    endtime = 0.0
    
    ## Finish render
    #
    # Self-explanatory.
    #
    def __finishrender__(self):
        self.context.pop_group_to_source()
        self.context.paint()
        
    def __render__(self):
        self.context.push_group()
        
        staticstates = filter(lambda x: x.__class__.__name__ == 'StaticState', self.states)
        
        if len(staticstates) > 1:
            raise errors.LayerError('Too many StaticStates')
        elif len(staticstates) < 0:
            raise errors.LayerError('Not enough StaticStates')
        
        animatedstates = sorted(filter(lambda x: x.__class__.__name__ == 'AnimatedState', self.states), key = lambda x: x.starttime)
        
        states = []
        
        states.extend(staticstates)
        states.extend(animatedstates)
        
        for s in states:
            if s.__class__.__name__ == 'StaticState' or (s.__class__.__name__ == 'AnimatedState' and self.frame >= (s.starttime * self.fps)):
                s.context, s.layer, s.fps, s.size, s.frame = self.context, self, self.fps, self.size, self.frame 
                s.__render__()
                del s.context, s.layer, s.fps, s.size, s.frame
        
## Basic state
#
# This class is used to set static and animated texts but should not be used directly. Elements are attached to this class and rendered.
#
# stateObject = State(elements=[elementObject])
#
class State(Renderable):
    
    ## List of elements attached to the state
    elements = []
    
    def __render__(self):
        self.layer.position = Layer.position
        self.context.identity_matrix()

## Basic element
#
# This class is used to define state behaviour but should not be used directly. Usage is according to that defined in extended elements.
#
class Element(Renderable):
    
    ## Precedence of rendering
    precedence = 0.0
    
    ## Element interpolate
    #
    # Interpolate between two instances of the element.
    # @param percent: Percentage (in float) to use.
    # @param start: Instance to start with.
    # @param end: Instance to end on.
    # @param accel: Accelerative exponent.
    #
    @staticmethod
    def __interpolate__(percent, start, end, accel): pass

## Basic pattern
#
# This class is used to define patterns but should not be used directly. Usage is according to that defined in extended textures.
#
class Pattern(Renderable):
    
    ## Naive interpolate
    #
    # Interpolate changes between patterns by blending (slow).
    #
    # @param percent: Percentage (in float) to use
    # @param start: Instance to start with.
    # @param end: Instance to end on.
    # @param accel: Accelerative exponent.
    @staticmethod
    def __naiveinterpolate__(percent, start, end, accel):
        tempsurf = cairo.ImageSurface(cairo.FORMAT_ARGB32, start.vidsize[0], end.vidsize[1])
        tempcontext = cairo.Context(tempsurf)
        tempcontext.set_source(start.__render__())
        tempcontext.paint_with_alpha(1.0 - percent)
        tempcontext.set_source(end.__render__())
        tempcontext.paint_with_alpha(percent)
        tempsurfpat = cairo.SurfacePattern(tempsurf)
        tempsurfpat.set_extend(cairo.EXTEND_PAD)
        return Pattern(__render__=lambda: tempsurfpat)
    
    @staticmethod
    def __interpolate__(percent, start, end, accel):
        if hasattr(start.__class__, '__interpolate__') and start.__class__.__interpolate__ is not Pattern.__interpolate__:
            return(start.__class__.__interpolate__(percent, start, end, accel))
        else:
            return(Pattern.__naiveinterpolate__(percent, start, end, accel))

## Basic path
#
# This class is used to draw paths but should not be used directly. Usage is according to that defined in extended paths.
#
class Path(Renderable):
    
    ## Path extents, in a 2-2-tuple formatted ((x1, y1), (x2, y2))
    pathextents = ((0.0, 0.0), (0.0, 0.0))
    
    ## Transforms to apply to the path
    transforms = []
    
    ## Get path extents
    #
    # Get path extents without invoking __render__.
    def path_extents(self):
        tempsurf = cairo.ImageSurface(cairo.FORMAT_A1, 0, 0)
        tempcontext = cairo.Context(tempsurf)
        
        self.__class__(context=tempcontext, **dict([(k, v) for k, v in self.__dict__.iteritems() if k != 'context' ])).__initrender__()
        
        rpe = tempcontext.path_extents()
        
        tempsurf.finish()
        
        return ((rpe[0], rpe[1]), (rpe[2], rpe[3]))
    
    ## Initialise render
    #
    # Stuff to do before rendering or calling extents functions.
    #
    def __initrender__(self): pass
    
    def __render__(self):
        self.pathextents = self.path_extents()
        
        for t in sorted(self.transforms, key = lambda x: x.precedence):
            t.context = self.context
            t.__render__()
            del t.context